<?php

namespace app\admin\utils\controller;

use app\admin\app\model\SystemFile;
use app\common\controller\BaseController;
use plugin\admin\app\model\Upload;
use plugin\filesystem\api\Config;
use plugin\filesystem\api\Storage;
use support\exception\ApiException;
use support\facade\Logger;
use support\Request;
use support\Response;
use Webman\Http\UploadFile;

class UploadController extends BaseController
{

    /**
     * 文件上传脚本

     */
//    public function index(): Response
//    {
//        $data = ['exts' => []];
//        foreach (str2arr(sysconf('storage.allow_exts')) as $ext) {
//            $data['exts'][$ext] = Storage::mime($ext);
//        }
//        $template = realpath(__DIR__ . '/../../view/api/upload.js');
//        $data['exts'] = json_encode($data['exts'], JSON_UNESCAPED_UNICODE);
//        $data['nameType'] = sysconf('storage.name_type') ?: 'xmd5';
//        return view($template, $data)->contentType('application/x-javascript');
//    }

    /**
     * 文件上传检查
     * @login true
     */
    public function state()
    {
        [$name, $safe] = [input('name'), $this->getSafe()];
        $data = ['storage' => $this->getType(), 'safe' => intval($safe), 'hash' => input('hash'),'server'=>''];
        $params = $this->_vali([
            'storage.value'   => $this->getType(),
            'admin_id.value'   => getAdminId(),
            'name.require' => '名称不能为空！',
            'hash.require' => '哈希不能为空！',
            'ext.require' => '后缀不能为空！',
            'file_size.require' => '大小不能为空！',
            'mime_type' => '',
            'status' => 1,
            'url'=>'',
            'created_at'=>date('Y-m-d'),
        ],'post');
        $file = SystemFile::where(['hash'=>$params['hash'],'status'=>2,'isfast'=>0,'storage'=>$this->getType()])->first();
        if(!empty($file)){
            $install = $file->toArray();
            unset($install['id']);
            $install['created_at'] = $install['updated_at']  = date('Y-m-d');
            $install['isfast'] = 1;
            $file_id = $file->insertGetId($install);
            $extr = ['id' => $file_id ?? 0,
                'url' => $install['url'],
                'full_url' => Storage::url($install['url']),
                'name'=>$install['name'],
                'isfast'=>1,
            ];
            return $this->success('文件已经上传', array_merge($data, $extr), 200);
        }else{
            $fileId = SystemFile::insertGetId($params);
            // 检查是否有已传分块 分片临时存储
            $index = $this->getOldPartIndex($params['hash']);
            return $this->json(404,'获取上传授权参数', ['id'=>$fileId,'partIndex'=>$index]);
        }
    }


    /**
     * 更新文件状态
     * @login true
     * @return void
     */
    public function done()
    {
        $data = $this->_vali([
            'id._'   => '编号不能为空！',
            'hash._' => '哈希不能为空！',
        ],'post');
        $file = SystemFile::where($data)->first();
        if (!$file) $this->fail('文件不存在！');
        $params = \request()->post();
        $file->status = 2;
        $file->updated_at = date('Y-m-d');
        $file->image_width = $params['image_width'];
        $file->image_height = $params['image_height'];
        $file->url = $params['url'];
        if ($file->save()) {
            return $this->success('更新成功！');
        } else {
            return $this->fail('更新失败！');
        }
    }


    /**
     * 文件上传入口
     * @login true
     */
    public function file(Request $request)
    {
        if (!($file = $this->getFile($request))->isValid()) {
            return $this->fail('文件上传异常，文件过大或未上传！');
        }

        $extension = strtolower($file->getExtension());
        if (in_array($extension, ['sh', 'asp', 'bat', 'cmd', 'exe', 'php'])) {
            return $this->fail('文件安全保护，禁止上传可执行文件！');
        }

        try {
            $result = Storage::upload($file);

            $jsonString = json_encode($result);
            $result = json_decode($jsonString, true);

            if ($result['file_url']) {
                // 如果是本地文件 需要检查是否是可执行文件
                if($result['adapter'] == 'public'){
                    // 文件检查  禁止上传可执行文件
                    $partFile = base_path().'/public/uploads/'.$result['file_name'];// 文件绝对路径
                    if($this->imgNotSafe($partFile) && unlink($partFile)){
                        return $this->json(400,"文件检查未通过！");
                    }
                }

                $resData = [
                    'url'=>$result['file_name'],
                    'storage'=>$result['adapter'],
                    'full_url'=>$result['file_url'],
                    'origin_name'=>$result['origin_name'],
                    'image_width'=>$result['file_width'],
                    'image_height'=>$result['file_height'],
                    'file_size'=>$result['size'],
                    'ext'=>$result['extension'],
                    'mime_type'=>$result['mime_type'],
                ];
//                var_dump(3333);

                return $this->success('ok',$resData);
            } else {
                return $this->fail('文件处理失败，请稍候再试！');
            }
        } catch (\Exception $exception) {
            return $this->fail($exception->getMessage());
        }
    }

    /**
     * 大文件  分片上传至本地
     * @param Request $request
     * @return Response
     */
    public function fileBig(Request $request){
        $fileName = $request->post('key'); // 文件名
        $extension = explode('.',$fileName)[1]??'';// 文件后缀
        if ($extension && in_array($extension, ['sh', 'asp', 'bat', 'cmd', 'exe', 'php'])) {
            return $this->fail('文件安全保护，禁止上传可执行文件！');
        }
        // 上传分片文件  真 全部上传  假 部分上传
        if($this->putFilePart($request)){
            // 合并分片文件
            return $this->mergePartFile($request,"uploads/storage/");
        }else{
            return $this->json(201,"分片上传成功");
        }
    }

    /**
     * 获取文件上传类型  1 安全模式
     * @return boolean
     */
    private function getSafe(): bool
    {
        return boolval(input('safe', '0'));
    }

    /**
     * 获取文件上传方式
     * @return string
     */
    private function getType(): string
    {
        $type = strtolower(input('uptype', ''));
        if (in_array($type, ['local', 'qiniu', 'alioss', 'txcos', 'uptype'])) {
            return $type;
        } else {
            $config = Config::getDbConfig();
            return strtolower($config['default']);
        }
    }

    /**
     * 获取本地文件对象
     * @return UploadFile|void
     */
    private function getFile(Request $request): UploadFile
    {
        $file = $request->file('file');
        if ($file instanceof UploadFile) {
            return $file;
        } else {
            throw new ApiException('未获取到上传的文件对象！');
        }
        return false;
    }

    /**
     * 检查图片是否安全
     * @param string $filename
     * @return boolean
     */
    private function imgNotSafe(string $filename): bool
    {
        $source = fopen($filename, 'rb');
        if (($size = filesize($filename)) > 512) {
            $hexs = bin2hex(fread($source, 512));
            fseek($source, $size - 512);
            $hexs .= bin2hex(fread($source, 512));
        } else {
            $hexs = bin2hex(fread($source, $size));
        }
        if (is_resource($source)) fclose($source);
        $bins = hex2bin($hexs);
        /* 匹配十六进制中的 <% ( ) %> 或 <? ( ) ?> 或 <script | /script> */
        foreach (['<?php ', '<% ', '<script '] as $key) if (stripos($bins, $key) !== false) return true;
        return preg_match("/(3c25.*?28.*?29.*?253e)|(3c3f.*?28.*?29.*?3f3e)|(3C534352495054)|(2F5343524950543E)|(3C736372697074)|(2F7363726970743E)/is", $hexs);
    }

    //获取历史分块位置 断点续传
    protected function getOldPartIndex($hash){
        $uploadTmpPath = base_path() . "/runtime/tmp/{$hash}/";
        if(!is_dir($uploadTmpPath)){
            return 0;

        }
        $files = scandir($uploadTmpPath);
        $parts = [];
        // 获取所有合理的分片
        foreach ($files as $file){
            if($file !='.' && $files!='..' && strpos($file,'.part')){
                $parts[] = $file;
            }
        }
        // 寻找中断的分片
        for($i=1;$i<10000;$i++){
            if(!in_array("{$i}.part",$parts)){
                return $i-1;
            }
        }
        return 0;// 无分片文件
    }

    // 保存分片文件
    protected function putFilePart(Request $request){
        $chunk = $request->file('chunk');// 分片数据
        $chunkNumber = $request->post('chunkNumber');// 分片序号
        $chunkAll = $request->post('chunkAll'); // 总片数
        $hash = $request->post('hash'); // 哈希

        // 分片临时存储
        $uploadTmpPath = base_path() . "/runtime/tmp/{$hash}/";
        mkdirs($uploadTmpPath);
        file_put_contents($uploadTmpPath."{$chunkNumber}.part",file_get_contents($chunk->getPathname()));
        return $chunkNumber == $chunkAll;
    }

    /**
     * 合并分片文件
     * @param Request $request 上下文
     * @param $catalog 文件存储目录
     * @return Response
     */
    protected function mergePartFile(Request $request,$catalog='uploads/storage/',$fileName=''){
        $fileName = $request->post('key'); // 文件名
        $hash = $request->post('hash'); // 哈希
        $chunkAll = $request->post('chunkAll'); // 总片数

        $uploadTmpPath = base_path() . "/runtime/tmp/{$hash}/";// 临时目录
        // 最终实际存储位置
        $targetPath = base_path()."/public/{$catalog}{$fileName}";// 实际目录
        mkdirs(dirname($targetPath));// 目录创建

        $finalFile = fopen($targetPath,'ab');
        for($i = 1;$i<=$chunkAll;$i++){
            $part = file_get_contents($uploadTmpPath."{$i}.part");
            fwrite($finalFile,$part);
            unlink($uploadTmpPath."{$i}.part");
        }
        fclose($finalFile);
        rmdir($uploadTmpPath);// 移除分片文件夹
        // 文件检查  禁止上传可执行文件
        if($this->imgNotSafe($targetPath) && unlink($targetPath)){
            return $this->json(400,"文件检查未通过！");
        }

        $imageInfo = getimagesize($targetPath);
        return $this->json(0,"ok",[
            'url'=>"/storage/{$fileName}",
            'image_width'=>$imageInfo[0]??0,
            'image_height'=>$imageInfo[1]??0,
            'storage'=>'public',// 只能是传自己本地
            'full_url'=>Storage::adapter('public')->url("/storage/{$fileName}"),
        ]);
    }

}